﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;

namespace GrapeCity.ExcelService
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "xsapi/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );

            config.Formatters.Remove(config.Formatters.XmlFormatter);
            config.Formatters.JsonFormatter.SerializerSettings.DateFormatHandling = Newtonsoft.Json.DateFormatHandling.MicrosoftDateFormat;

            var appSetting = System.Web.Configuration.WebConfigurationManager.AppSettings;
            if (appSetting.Count > 0)
            {
                if (appSetting.GetBooleanOrDefault("RequireHttps"))
                {
                    config.Filters.Add(new RequireHttpsAttribute());
                }
                if (appSetting.GetBooleanOrDefault("EnableCors"))
                {
                    config.MessageHandlers.Add(new CorsProcessHandler());
                }
                if (appSetting.GetBooleanOrDefault("UseDynamicCompress"))
                {
                    config.MessageHandlers.Add(new DynamicCompressHandler());
                }
            }
            else
            {
                if (config.Properties.ContainsKey("RequireHttps") && Convert.ToBoolean(config.Properties["RequireHttps"]))
                {
                    config.Filters.Add(new RequireHttpsAttribute());
                }
                if (config.Properties.ContainsKey("EnableCors") && Convert.ToBoolean(config.Properties["EnableCors"]))
                {
                    config.MessageHandlers.Add(new CorsProcessHandler());
                }
                if (config.Properties.ContainsKey("UseDynamicCompress") && Convert.ToBoolean(config.Properties["UseDynamicCompress"]))
                {
                    config.MessageHandlers.Add(new DynamicCompressHandler());
                }
            }

            config.MessageHandlers.Add(new IE9FixHandler());

            // Uncomment the following line of code to enable query support for actions with an IQueryable or IQueryable<T> return type.
            // To avoid processing unexpected or malicious queries, use the validation settings on QueryableAttribute to validate incoming queries.
            // For more information, visit http://go.microsoft.com/fwlink/?LinkId=279712.
            //config.EnableQuerySupport();

            // To disable tracing in your application, please comment out or remove the following line of code
            // For more information, refer to: http://www.asp.net/web-api
            config.EnableSystemDiagnosticsTracing();
        }

        private static bool GetBooleanOrDefault(this NameValueCollection settings, string key)
        {
            var sValue = settings.Get(key);
            bool bValue;
            if (!string.IsNullOrEmpty(sValue)
                && bool.TryParse(sValue, out bValue))
            {
                return bValue;
            }
            return false;
        }

        internal class IE9FixHandler : DelegatingHandler
        {
            protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
            {
                var response = await base.SendAsync(request, cancellationToken);

                var ua = request.Headers.UserAgent.ToString();
                var userBrowser = new HttpBrowserCapabilities { Capabilities = new System.Collections.Hashtable { { string.Empty, ua } } };
                var factory = new System.Web.Configuration.BrowserCapabilitiesFactory();
                factory.ConfigureBrowserCapabilities(new NameValueCollection(), userBrowser);
                var isIE = string.Compare(userBrowser.Browser, "IE", true) == 0;
                var ver = new Version(userBrowser.Version);
                var lowIE = isIE && ver.Major <= 9;
                if (lowIE)
                {
                    response.Content.Headers.ContentType.MediaType = "text/plain";
                }

                return response;
            }
        }

        internal class CorsProcessHandler : DelegatingHandler
        {
            protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
                CancellationToken cancellationToken)
            {
                var response = await base.SendAsync(request, cancellationToken);
                response.Headers.Add("Access-Control-Allow-Origin", "*");
                return response;
            }
        }

        internal class DynamicCompressHandler : DelegatingHandler
        {
            protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
                CancellationToken cancellationToken)
            {
                var response = await base.SendAsync(request, cancellationToken);
                var acceptEncoding = request.Headers.AcceptEncoding.FirstOrDefault((v) => v.Value == "gzip" || v.Value == "deflate");
                if (acceptEncoding != null && !string.IsNullOrEmpty(acceptEncoding.Value)
                    && response.Content != null
                    && response.Content.Headers != null
                    && response.Content.Headers.ContentType != null
                    && !string.IsNullOrEmpty(response.Content.Headers.ContentType.MediaType)
                    && response.Content.Headers.ContentType.MediaType.ToLower() == "application/json")
                {
                    response.Content = new CompressedContent(response.Content, acceptEncoding.Value);
                }
                return response;
            }

            internal class CompressedContent : HttpContent
            {
                private HttpContent _originalContent;
                private string _encodingType;

                public CompressedContent(HttpContent content, string encodingType)
                {
                    if (content == null)
                    {
                        throw new ArgumentNullException("content");
                    }
                    if (encodingType == null)
                    {
                        throw new ArgumentNullException("encodingType");
                    }

                    _originalContent = content;
                    this._encodingType = encodingType.ToLowerInvariant();

                    if (this._encodingType != "gzip" && this._encodingType != "deflate")
                    {
                        throw new InvalidOperationException(
                            string.Format("Encoding '{0}' is not supported. Only supports gzip or deflate encoding.", this._encodingType));
                    }

                    // copy the headers from the original content
                    foreach (KeyValuePair<string, IEnumerable<string>> header in _originalContent.Headers)
                    {
                        if (header.Key.ToLower() != "content-length")
                        {
                            this.Headers.TryAddWithoutValidation(header.Key, header.Value);
                        }
                    }
                    this.Headers.ContentEncoding.Add(encodingType);
                }

                protected override bool TryComputeLength(out long length)
                {
                    length = -1;
                    return false;
                }

                protected override Task SerializeToStreamAsync(System.IO.Stream stream, TransportContext context)
                {
                    System.IO.Stream compressedStream = null;

                    if (_encodingType == "gzip")
                    {
                        compressedStream = new GZipStream(stream, CompressionMode.Compress, leaveOpen: true);
                    }
                    else if (_encodingType == "deflate")
                    {
                        compressedStream = new DeflateStream(stream, CompressionMode.Compress, leaveOpen: true);
                    }
                    return _originalContent.CopyToAsync(compressedStream).ContinueWith(tsk =>
                    {
                        if (compressedStream != null)
                        {
                            compressedStream.Dispose();
                        }
                    });
                }
            }
        }

        internal class RequireHttpsAttribute : AuthorizationFilterAttribute
        {
            public override void OnAuthorization(HttpActionContext actionContext)
            {
                if (actionContext == null)
                {
                    throw new ArgumentNullException("actionContext");
                }
                if (!String.Equals(actionContext.Request.RequestUri.Scheme, "https", StringComparison.OrdinalIgnoreCase))
                {
                    actionContext.Response = new HttpResponseMessage(HttpStatusCode.BadRequest)
                    {
                        Content = new StringContent("HTTPS Required")
                    };
                }
            }
        }
    }
}
